/*
* pyScss, a Scss compiler for Python
* SCSS blocks scanner.
*
* German M. Bravo (Kronuz) <german.mb@gmail.com>
* https://github.com/Kronuz/pyScss
*
* MIT license (http://www.opensource.org/licenses/mit-license.php)
* Copyright (c) 2011 German M. Bravo (Kronuz), All rights reserved.
*/
#include <Python.h>
#include "block_locator.h"
#include "scanner.h"

/* BlockLocator */
staticforward PyTypeObject scss_BlockLocatorType;

typedef struct {
	PyObject_HEAD
	BlockLocator *locator;
} scss_BlockLocator;

static int
scss_BlockLocator_init(scss_BlockLocator *self, PyObject *args, PyObject *kwds)
{
	char *codestr;
	int codestr_sz;

	self->locator = NULL;

	if (!PyArg_ParseTuple(args, "s#", &codestr, &codestr_sz)) {
		return -1;
	}

	self->locator = BlockLocator_new(codestr, codestr_sz);

	#ifdef DEBUG
		PySys_WriteStderr("Scss BlockLocator object initialized! (%lu bytes)\n", sizeof(scss_BlockLocator));
	#endif

	return 0;
}

static void
scss_BlockLocator_dealloc(scss_BlockLocator *self)
{
	if (self->locator != NULL) BlockLocator_del(self->locator);

	self->ob_type->tp_free((PyObject*)self);

	#ifdef DEBUG
		PySys_WriteStderr("Scss BlockLocator object destroyed!\n");
	#endif
}

scss_BlockLocator*
scss_BlockLocator_iter(scss_BlockLocator *self)
{
	Py_INCREF(self);
	return self;
}

PyObject*
scss_BlockLocator_iternext(scss_BlockLocator *self)
{
	Block *block;

	if (self->locator != NULL) {
		block = BlockLocator_iternext(self->locator);

		if (block->error > 0) {
			return Py_BuildValue(
				"is#s#",
				block->lineno,
				block->selprop,
				block->selprop_sz,
				block->codestr,
				block->codestr_sz
			);
		}

		if (block->error > 0) {
			PyErr_SetString(PyExc_Exception, self->locator->exc);
			return NULL;
		}
	}

	/* Raising of standard StopIteration exception with empty value. */
	PyErr_SetNone(PyExc_StopIteration);
	return NULL;
}

/* Type definition */

static PyTypeObject scss_BlockLocatorType = {
	PyObject_HEAD_INIT(NULL)
	0,                                         /* ob_size */
	"scss._BlockLocator",                      /* tp_name */
	sizeof(scss_BlockLocator),                 /* tp_basicsize */
	0,                                         /* tp_itemsize */
	(destructor)scss_BlockLocator_dealloc,     /* tp_dealloc */
	0,                                         /* tp_print */
	0,                                         /* tp_getattr */
	0,                                         /* tp_setattr */
	0,                                         /* tp_compare */
	0,                                         /* tp_repr */
	0,                                         /* tp_as_number */
	0,                                         /* tp_as_sequence */
	0,                                         /* tp_as_mapping */
	0,                                         /* tp_hash  */
	0,                                         /* tp_call */
	0,                                         /* tp_str */
	0,                                         /* tp_getattro */
	0,                                         /* tp_setattro */
	0,                                         /* tp_as_buffer */
	Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_ITER, /* tp_flags */
	"Internal BlockLocator iterator object.",  /* tp_doc */
	0,                                         /* tp_traverse */
	0,                                         /* tp_clear */
	0,                                         /* tp_richcompare */
	0,                                         /* tp_weaklistoffset */
	(getiterfunc)scss_BlockLocator_iter,       /* tp_iter: __iter__() method */
	(iternextfunc)scss_BlockLocator_iternext,  /* tp_iternext: next() method */
	0,                                         /* tp_methods */
	0,                                         /* tp_members */
	0,                                         /* tp_getset */
	0,                                         /* tp_base */
	0,                                         /* tp_dict */
	0,                                         /* tp_descr_get */
	0,                                         /* tp_descr_set */
	0,                                         /* tp_dictoffset */
	(initproc)scss_BlockLocator_init,          /* tp_init */
};


/* Scanner */
static PyObject *PyExc_scss_NoMoreTokens;

staticforward PyTypeObject scss_ScannerType;

typedef struct {
	PyObject_HEAD
	Scanner *scanner;
} scss_Scanner;


static PyObject *
scss_Scanner_rewind(scss_Scanner *self, PyObject *args)
{
	int token_num;
	if (self->scanner != NULL) {
		if (PyArg_ParseTuple(args, "i", &token_num)) {
			Scanner_rewind(self->scanner, token_num);
		}
	}
	Py_INCREF(Py_None);
	return (PyObject *)Py_None;
}


static PyObject *
scss_Scanner_token(scss_Scanner *self, PyObject *args)
{
	PyObject *item;
	int i, is_tuple;
	long size;

	Token *p_token;

	int token_num;
	PyObject *restrictions;
	Pattern *_restrictions = NULL;
	int restrictions_sz = 0;
	if (self->scanner != NULL) {
		if (PyArg_ParseTuple(args, "i|O", &token_num, &restrictions)) {
			is_tuple = PyTuple_Check(restrictions);
			if (is_tuple || PyList_Check(restrictions)) {
				size = is_tuple ? PyTuple_Size(restrictions) : PyList_Size(restrictions);
				_restrictions = PyMem_New(Pattern, size);
				for (i = 0; i < size; ++i) {
					item = is_tuple ? PyTuple_GetItem(restrictions, i) : PyList_GetItem(restrictions, i);
					if (PyString_Check(item)) {
						_restrictions[restrictions_sz].tok = PyString_AsString(item);
						_restrictions[restrictions_sz].expr = NULL;
						restrictions_sz++;
					}
				}
			}
			p_token = Scanner_token(self->scanner, token_num, _restrictions, restrictions_sz);

			if (_restrictions != NULL) PyMem_Del(_restrictions);

			if (p_token == (Token *)SCANNER_EXC_BAD_TOKEN) {
				PyErr_SetString(PyExc_SyntaxError, self->scanner->exc);
				return NULL;
			}
			if (p_token == (Token *)SCANNER_EXC_RESTRICTED) {
				PyErr_SetString(PyExc_SyntaxError, self->scanner->exc);
				return NULL;
			}
			if (p_token == (Token *)SCANNER_EXC_UNIMPLEMENTED) {
				PyErr_SetString(PyExc_NotImplementedError, self->scanner->exc);
				return NULL;
			}
			if (p_token == (Token *)SCANNER_EXC_NO_MORE_TOKENS) {
				PyErr_SetNone(PyExc_scss_NoMoreTokens);
				return NULL;
			}
			if (p_token < 0) {
				PyErr_SetNone(PyExc_Exception);
				return NULL;
			}
			return Py_BuildValue(
				"iiss#",
				p_token->string - self->scanner->input,
				p_token->string - self->scanner->input + p_token->string_sz,
				p_token->regex->tok,
				p_token->string,
				p_token->string_sz
			);
		}
	}
	Py_INCREF(Py_None);
	return (PyObject *)Py_None;
}

static PyObject *
scss_Scanner_reset(scss_Scanner *self, PyObject *args, PyObject *kwds)
{
	char *input = NULL;
	int input_sz = 0;

	if (self->scanner != NULL) {
		if (PyArg_ParseTuple(args, "|z#", &input, &input_sz)) {
			Scanner_reset(self->scanner, input, input_sz);
		}
	}

	Py_INCREF(Py_None);
	return (PyObject *)Py_None;
}

static PyObject *
scss_Scanner_setup_patterns(PyObject *self, PyObject *patterns)
{
	PyObject *item, *item0, *item1;
	int i, is_tuple, _is_tuple;
	long size;

	Pattern *_patterns = NULL;
	int patterns_sz = 0;
	if (!Scanner_initialized()) {
		is_tuple = PyTuple_Check(patterns);
		if (is_tuple || PyList_Check(patterns)) {
			size = is_tuple ? PyTuple_Size(patterns) : PyList_Size(patterns);
			_patterns = PyMem_New(Pattern, size);
			for (i = 0; i < size; ++i) {
				item = is_tuple ? PyTuple_GetItem(patterns, i) : PyList_GetItem(patterns, i);
				_is_tuple = PyTuple_Check(item);
				if (_is_tuple || PyList_Check(item)) {
					item0 = _is_tuple ? PyTuple_GetItem(item, 0) : PyList_GetItem(item, 0);
					item1 = _is_tuple ? PyTuple_GetItem(item, 1) : PyList_GetItem(item, 1);
					if (PyString_Check(item0) && PyString_Check(item1)) {
						_patterns[patterns_sz].tok = PyString_AsString(item0);
						_patterns[patterns_sz].expr = PyString_AsString(item1);
						patterns_sz++;
					}
				}
			}
		}
		Scanner_initialize(_patterns, patterns_sz);
		if (_patterns != NULL) PyMem_Del(_patterns);
	}
	Py_INCREF(Py_None);
	return (PyObject *)Py_None;
}

static int
scss_Scanner_init(scss_Scanner *self, PyObject *args, PyObject *kwds)
{
	PyObject *item, *item0, *item1;
	int i, is_tuple, _is_tuple;
	long size;

	PyObject *patterns, *ignore;
	Pattern *_patterns = NULL;
	int patterns_sz = 0;
	Pattern *_ignore = NULL;
	int ignore_sz = 0;
	char *input = NULL;
	int input_sz = 0;

	self->scanner = NULL;

	if (!PyArg_ParseTuple(args, "OO|z#", &patterns, &ignore, &input, &input_sz)) {
		return -1;
	}

	if (!Scanner_initialized()) {
		is_tuple = PyTuple_Check(patterns);
		if (is_tuple || PyList_Check(patterns)) {
			size = is_tuple ? PyTuple_Size(patterns) : PyList_Size(patterns);
			_patterns = PyMem_New(Pattern, size);
			for (i = 0; i < size; ++i) {
				item = is_tuple ? PyTuple_GetItem(patterns, i) : PyList_GetItem(patterns, i);
				_is_tuple = PyTuple_Check(item);
				if (_is_tuple || PyList_Check(item)) {
					item0 = _is_tuple ? PyTuple_GetItem(item, 0) : PyList_GetItem(item, 0);
					item1 = _is_tuple ? PyTuple_GetItem(item, 1) : PyList_GetItem(item, 1);
					if (PyString_Check(item0) && PyString_Check(item1)) {
						_patterns[patterns_sz].tok = PyString_AsString(item0);
						_patterns[patterns_sz].expr = PyString_AsString(item1);
						patterns_sz++;
					}
				}
			}
		}
		Scanner_initialize(_patterns, patterns_sz);
	}

	is_tuple = PyTuple_Check(ignore);
	if (is_tuple || PyList_Check(ignore)) {
		size = is_tuple ? PyTuple_Size(ignore) : PyList_Size(ignore);
		_ignore = PyMem_New(Pattern, size);
		for (i = 0; i < size; ++i) {
			item = is_tuple ? PyTuple_GetItem(ignore, i) : PyList_GetItem(ignore, i);
			if (PyString_Check(item)) {
				_ignore[ignore_sz].tok = PyString_AsString(item);
				_ignore[ignore_sz].expr = NULL;
				ignore_sz++;
			}
		}
	}

	self->scanner = Scanner_new(_patterns, patterns_sz, _ignore, ignore_sz, input, input_sz);

	if (_patterns != NULL) PyMem_Del(_patterns);
	if (_ignore != NULL) PyMem_Del(_ignore);

	#ifdef DEBUG
		PySys_WriteStderr("Scss Scanner object initialized! (%lu bytes)\n", sizeof(scss_Scanner));
	#endif

	return 0;
}

static PyObject *
scss_Scanner_repr(scss_Scanner *self)
{
	/* Print the last 10 tokens that have been scanned in */
	PyObject *repr, *tmp;
	Token *p_token;
	int i, start, pos;

	if (self->scanner != NULL && self->scanner->tokens_sz) {
		start = self->scanner->tokens_sz - 10;
		repr = PyString_FromString("");
		for (i = (start < 0) ? 0 : start; i < self->scanner->tokens_sz; i++) {
			p_token = &self->scanner->tokens[i];
			PyString_ConcatAndDel(&repr, PyString_FromString("\n"));
			pos = (int)(p_token->string - self->scanner->input);
			PyString_ConcatAndDel(&repr, PyString_FromFormat("  (@%d)  %s  =  ",
				pos, p_token->regex->tok));
			tmp = PyString_FromStringAndSize(p_token->string, p_token->string_sz);
			PyString_ConcatAndDel(&repr, PyObject_Repr(tmp));
			Py_XDECREF(tmp);
		}
	} else {
		repr = PyString_FromString("None");
	}

	return (PyObject *)repr;

/*
	PyObject *repr, *tmp, *tmp2;
	Token *p_token;
	char *tok;
	int i, start, first = 1, cur, max=0, pos;

	if (self->scanner != NULL && self->scanner->tokens_sz) {
		start = self->scanner->tokens_sz - 10;
		repr = PyString_FromString("");
		for (i = (start < 0) ? 0 : start; i < self->scanner->tokens_sz; i++) {
			p_token = self->scanner->tokens[i];
			PyString_ConcatAndDel(&repr, PyString_FromString("\n"));
			pos = (int)(p_token->string - self->scanner->input);
			PyString_ConcatAndDel(&repr, PyString_FromFormat("  (@%d)  %s  =  ",
				pos, p_token->regex->tok));
			tmp = PyString_FromString(p_token->string);
			PyString_ConcatAndDel(&repr, PyObject_Repr(tmp));
			Py_XDECREF(tmp);
		}

		start = self->scanner->tokens_sz - 10;
		for (i = (start < 0) ? 0 : start; i < self->scanner->tokens_sz; i++) {
			p_token = self->scanner->tokens[i];
			cur = strlen(p_token->regex->tok) * 2;
			if (cur > max) max = cur;
		}
		tok = PyMem_New(char, max + 4);
		repr = PyString_FromString("");
		for (i = (start < 0) ? 0 : start; i < self->scanner->tokens_sz; i++) {
			p_token = self->scanner->tokens[i];
			if (!first) PyString_ConcatAndDel(&repr, PyString_FromString("\n"));

			pos = (int)(p_token->string - self->scanner->input);
			if (pos < 10) PyString_ConcatAndDel(&repr, PyString_FromString(" "));
			if (pos < 100) PyString_ConcatAndDel(&repr, PyString_FromString(" "));
			if (pos < 1000) PyString_ConcatAndDel(&repr, PyString_FromString(" "));
			PyString_ConcatAndDel(&repr, PyString_FromFormat("(@%d)  ",
				pos));

			tmp = PyString_FromString(p_token->regex->tok);
			tmp2 = PyObject_Repr(tmp);
			memset(tok, ' ', max + 4);
			tok[max + 3 - PyString_Size(tmp2)] = '\0';
			PyString_ConcatAndDel(&repr, PyString_FromString(tok));
			PyString_ConcatAndDel(&repr, tmp2);
			Py_XDECREF(tmp);

			PyString_ConcatAndDel(&repr, PyString_FromString("  =  "));
			tmp = PyString_FromString(p_token->string);
			PyString_ConcatAndDel(&repr, PyObject_Repr(tmp));
			Py_XDECREF(tmp);

			first = 0;
		}
		PyMem_Del(tok);
	} else {
		repr = PyString_FromString("None");
	}

	return (PyObject *)repr;
*/
}

static void
scss_Scanner_dealloc(scss_Scanner *self)
{
	if (self->scanner != NULL) Scanner_del(self->scanner);

	self->ob_type->tp_free((PyObject*)self);

	#ifdef DEBUG
		PySys_WriteStderr("Scss Scanner object destroyed!\n");
	#endif
}

static PyMethodDef scss_Scanner_methods[] = {
	{"reset", (PyCFunction)scss_Scanner_reset, METH_VARARGS, "Scan the next token"},
	{"token", (PyCFunction)scss_Scanner_token, METH_VARARGS, "Get the nth token"},
	{"rewind", (PyCFunction)scss_Scanner_rewind, METH_VARARGS, "Rewind scanner"},
	{"setup_patterns", (PyCFunction)scss_Scanner_setup_patterns, METH_O | METH_STATIC, "Initialize patterns."},
	{NULL, NULL, 0, NULL}        /* Sentinel */
};

static PyTypeObject scss_ScannerType = {
	PyObject_HEAD_INIT(NULL)
	0,                                         /* ob_size */
	"scss.Scanner",                            /* tp_name */
	sizeof(scss_Scanner),                      /* tp_basicsize */
	0,                                         /* tp_itemsize */
	(destructor)scss_Scanner_dealloc,          /* tp_dealloc */
	0,                                         /* tp_print */
	0,                                         /* tp_getattr */
	0,                                         /* tp_setattr */
	0,                                         /* tp_compare */
	(reprfunc)scss_Scanner_repr,               /* tp_repr */
	0,                                         /* tp_as_number */
	0,                                         /* tp_as_sequence */
	0,                                         /* tp_as_mapping */
	0,                                         /* tp_hash  */
	0,                                         /* tp_call */
	0,                                         /* tp_str */
	0,                                         /* tp_getattro */
	0,                                         /* tp_setattro */
	0,                                         /* tp_as_buffer */
	Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,  /* tp_flags */
	"Scanner object.",                         /* tp_doc */
	0,                                         /* tp_traverse */
	0,                                         /* tp_clear */
	0,                                         /* tp_richcompare */
	0,                                         /* tp_weaklistoffset */
	0,                                         /* tp_iter: __iter__() method */
	0,                                         /* tp_iternext: next() method */
	scss_Scanner_methods,                      /* tp_methods */
	0,                                         /* tp_members */
	0,                                         /* tp_getset */
	0,                                         /* tp_base */
	0,                                         /* tp_dict */
	0,                                         /* tp_descr_get */
	0,                                         /* tp_descr_set */
	0,                                         /* tp_dictoffset */
	(initproc)scss_Scanner_init,               /* tp_init */
};


/* Python constructor */

static PyObject *
scss_locate_blocks(PyObject *self, PyObject *args)
{
	scss_BlockLocator *result = NULL;

	result = PyObject_New(scss_BlockLocator, &scss_BlockLocatorType);
	if (result) {
		scss_BlockLocator_init(result, args, NULL);
	}

	return (PyObject *)result;
}


/* Module functions */

static PyMethodDef scss_methods[] = {
	{"locate_blocks", (PyCFunction)scss_locate_blocks, METH_VARARGS, "Locate Scss blocks."},
	{NULL, NULL, 0, NULL}        /* Sentinel */
};


/* Module init function */

PyMODINIT_FUNC
init_speedups(void)
{
	PyObject* m;

	scss_BlockLocatorType.tp_new = PyType_GenericNew;
	if (PyType_Ready(&scss_BlockLocatorType) < 0)
		return;

	scss_ScannerType.tp_new = PyType_GenericNew;
	if (PyType_Ready(&scss_ScannerType) < 0)
		return;

	BlockLocator_initialize();
	Scanner_initialize(NULL, 0);

	m = Py_InitModule("_speedups", scss_methods);

	Py_INCREF(&scss_BlockLocatorType);
	PyModule_AddObject(m, "_BlockLocator", (PyObject *)&scss_BlockLocatorType);

	Py_INCREF(&scss_ScannerType);
	PyModule_AddObject(m, "Scanner", (PyObject *)&scss_ScannerType);

	PyExc_scss_NoMoreTokens = PyErr_NewException("_speedups.NoMoreTokens", NULL, NULL);
	Py_INCREF(PyExc_scss_NoMoreTokens);
	PyModule_AddObject(m, "NoMoreTokens", (PyObject *)PyExc_scss_NoMoreTokens);
}
